﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Infrastructure.CrossCutting.Converters;
using Infrastructure.CrossCutting.Messages;

namespace Infrastructure.CrossCutting.Arguments
{
    public class Arguments
    {
        /// <summary>
        /// Prefix for short Argument Name
        /// e.g. -c:123
        /// </summary>
        [DefaultValue('-')]
        public char ShortPrefix { get; set; }

        /// <summary>
        /// Prefix for long Argument Name
        /// e.g. --componentId:123
        /// </summary>
        [DefaultValue("--")]
        public string LongPrefix { get; set; }

        /// <summary>
        /// Argument Value Separatorshort Argument Name
        /// e.g. -c:123
        /// </summary>
        [DefaultValue(":")]
        public string Separator { get; set; }

        public Dictionary<string, string> KeyValueDic = new Dictionary<string, string>();

        private Arguments(char shortPrefix, string longPrefix, string separator)
        {
            ShortPrefix = shortPrefix;
            LongPrefix = longPrefix;
            Separator = separator;
        }

        public static BoolMessageItem<Arguments>  Accept(string[] args, char shortPrefix, string longPrefix, string separator, object reciever)
        {
            BoolMessageItem<Arguments> parseResult = Parse(args, shortPrefix, longPrefix, separator);
            if (!parseResult.Success)
                return parseResult;

            BoolMessage validationResult = ApplyArgumentsToReceiver(parseResult.Item, reciever);

            return validationResult.Success
                       ? BoolMessageItem<Arguments>.True.SetItem(parseResult.Item)
                       : BoolMessageItem<Arguments>.False.SetMessage(validationResult.Message);
        }

        private static BoolMessageItem<Arguments> Parse(IEnumerable<string> args, char shortPrefix, string longPrefix, string separator)
        {
            //const string shortNamePattern = @"(?<name>[a-zA-Z0-9\-_\.]{1})";
            const string shortNamePattern = @"(?<name>[a-zA-Z]{1})";
            const string longNamePattern = @"(?<name>[a-zA-Z]+)";
            const string valuePattern = @"(?<value>.+)";

            //const string pattern1 = @"{0}(?<name>[a-zA-Z0-9\-_\.]+){1}(?<value>.+)";
            //const string pattern2 = @"{0}(?<name>[a-zA-Z0-9\-_\.]+)";

            string shortKeyBoolPattern = shortPrefix + shortNamePattern;
            string longKeyBoolPattern = longPrefix + longNamePattern;

            string shortKeyValuePattern = shortKeyBoolPattern + separator + valuePattern;
            string longKeyValuePattern = longKeyBoolPattern + separator + valuePattern;

            var resultArgs = new Arguments(shortPrefix, longPrefix, separator);

            IList<string> errors = new List<string>();

            foreach (var arg in args)
            {
                Match matchLongKeyValue = Regex.Match(arg, longKeyValuePattern);
                Match matchLongBool = Regex.Match(arg, longKeyBoolPattern);
                Match matchShortKeyValue = Regex.Match(arg, shortKeyValuePattern);
                Match matchShortBool = Regex.Match(arg, shortKeyBoolPattern);

                if(matchLongKeyValue.Success)
                {
                    string name = matchLongKeyValue.Groups["name"].Value;
                    string value = matchLongKeyValue.Groups["value"].Value;
                    resultArgs.KeyValueDic[name] = value;
                }
                else if (matchLongBool.Success)
                {
                    string name = matchLongBool.Groups["name"].Value;
                    const string value = "true";
                    resultArgs.KeyValueDic[name] = value;
                }
                else if(matchShortKeyValue.Success)
                {
                    string name = matchShortKeyValue.Groups["name"].Value;
                    string value = matchShortKeyValue.Groups["value"].Value;
                    resultArgs.KeyValueDic[name] = value;
                }
                else if (matchShortBool.Success)
                {
                    string name = matchShortBool.Groups["name"].Value;
                    const string value = "true";
                    resultArgs.KeyValueDic[name] = value;
                }
                else
                {
                    errors.Add("Unable to parse the Argument "+arg);
                }
            }

            return errors.Count > 0 ? 
                BoolMessageItem<Arguments>.False.SetMessage(string.Join(Environment.NewLine, errors)) : 
                BoolMessageItem<Arguments>.True.SetItem(resultArgs);
        }

        private static BoolMessage ApplyArgumentsToReceiver(Arguments args, object receiver)
        {
            PropertyInfo[] properties = receiver.GetType().GetProperties();
            
            IList<string> errors = new List<string>();

            foreach (var propertyInfo in properties)
            {
                object[] objs = propertyInfo.GetCustomAttributes(typeof (ArgumentAttribute), true);

                if (objs.Length > 0)
                {
                    var argAttribute = objs[0] as ArgumentAttribute;
                    if (argAttribute != null)
                    {
                        string value = (from key in args.KeyValueDic.Keys
                                        where key.Equals(argAttribute.ShortName) || key.Equals(argAttribute.LongName)
                                        select args.KeyValueDic[key]).FirstOrDefault();

                        BoolMessage validationResult = argAttribute.Validate(value);

                        if (!validationResult.Success)
                            errors.Add(validationResult.Message);
                        else if (errors.Count == 0)
                            propertyInfo.SetValue(receiver, DataTypeConverter.ConvertTo(argAttribute.ValueType, value));
                    }
                }
            }

            return errors.Count == 0
                       ? BoolMessage.True
                       : BoolMessage.False.SetMessage(string.Join(Environment.NewLine, errors));
        }
    }
}
